/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.redis.proxy.server.mgr;

import com.redis.proxy.server.alarm.ProxyRdsAlarmService;
import com.baidu.disconf.client.common.annotations.DisconfFile;
import com.baidu.disconf.client.common.annotations.DisconfUpdateService;
import com.baidu.disconf.client.common.update.IDisconfUpdate;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.redis.proxy.common.consts.CacheConsts;
import com.talk51.redis.proxy.jedis.pool.ProxyShardedJedisPool;
import com.talk51.redis.proxy.jedis.pool.alarm.RedisAlarm;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisShardInfo;

/**
 * redis config 类
 *
 * @author zhanggaofeng
 */
@Service
@Scope("singleton")
@DisconfFile(filename = "redis.properties")
@DisconfUpdateService(classes = {JedisManager.class})
public class JedisManager extends AbstDisconf implements IDisconfUpdate {

        private final Logger logger = LoggerFactory.getLogger(JedisManager.class);
        private final Map<String, ProxyShardedJedisPool> poolMap = Maps.newConcurrentMap();
        private final Map<String, String> hostMap = Maps.newConcurrentMap();
        private final RedisAlarm alarm;

        @Autowired
        public JedisManager(ProxyRdsAlarmService alarmService) {
                this.alarm = alarmService;
                try {
                        this.fileLoad();
                } catch (Exception ex) {
                        ex.printStackTrace();
                }
        }

        /**
         * 获取模块对应的redis连接
         *
         * @param redisKey
         * @return
         */
        public ProxyShardedJedisPool getShardedJedisPool(String redisKey) {
                int pos = redisKey.indexOf('_');
                if (pos <= 0) {
                        logger.error("key no contains module name ; key = " + redisKey);
                        return null;
                }
                return poolMap.get(redisKey.substring(0, pos));
        }

        @PreDestroy
        public void destory() {
                for (ProxyShardedJedisPool pool : poolMap.values()) {
                        pool.close();
                }
        }

        @Override
        public void reload() throws Exception {
                this.fileLoad();
        }

        @Override
        protected void updateCallBack() {

                Set<String> newModules = Sets.newHashSet(Arrays.asList(getString("modules", "").split(",")));
                Set<String> oldModules = hostMap.keySet();
                for (String moduleName : oldModules) {
                        // 删除旧的模块
                        if (!newModules.contains(moduleName)) {
                                hostMap.remove(moduleName);
                                ProxyShardedJedisPool pool = poolMap.remove(moduleName);
                                if (pool != null) {
                                        pool.close();
                                }
                        }
                }
                for (String moduleName : newModules) {
                        // 增加新的
                        if (!oldModules.contains(moduleName)) {
                                try {
                                        addModuleCache(moduleName, getString("module." + moduleName, ""));
                                } catch (Exception e) {
                                        logger.error(e.getMessage() + "");
                                }
                        }
                }
                // 检查缓存配置变化
                for (String moduleName : oldModules) {
                        String newHostStr = getString("module." + moduleName, "");
                        if (!newHostStr.equals(hostMap.get(moduleName))) {
                                addModuleCache(moduleName, newHostStr);
                        }
                }
        }

        /**
         * 增加模块缓存
         *
         * @param moduleName
         */
        private void addModuleCache(String moduleName, String hostStr) {
                List<JedisShardInfo> shards = Lists.newArrayList();
                String[] hosts = hostStr.split(",");

                /**
                 * 建议配置上redis的name,不配的话系统会根据module加下标拼接成name,这样的话redis
                 * host更改不影响已有redis的命中
                 */
                for (int idx = 0; idx < hosts.length; idx++) {
                        String[] infos = hosts[idx].split(":");
                        if (infos.length == 2) {
                                shards.add(new JedisShardInfo(infos[0], Integer.parseInt(infos[1]), moduleName + idx));
                        } else {
                                shards.add(new JedisShardInfo(infos[0], Integer.parseInt(infos[1]), infos[2]));
                        }
                }
                if (!shards.isEmpty()) {
                        JedisPoolConfig poolConfig = new JedisPoolConfig();
                        poolConfig.setMaxTotal(CacheConsts.SERVER_MAX_THREADS);
                        poolConfig.setMaxIdle(CacheConsts.SERVER_MAX_THREADS);
                        poolConfig.setMaxWaitMillis(getInt("redis.maxWaitMillis"));
                        poolConfig.setTestWhileIdle(true);
                        poolConfig.setTimeBetweenEvictionRunsMillis(getIntMinDefault("redis.timeBetweenEvictionRunsMillis", 5000));
                        poolConfig.setMinEvictableIdleTimeMillis(getInt("redis.minEvictableIdleTimeMillis"));
                        hostMap.put(moduleName, hostStr);
                        ProxyShardedJedisPool oldPool = poolMap.remove(moduleName);
                        poolMap.put(moduleName, new ProxyShardedJedisPool(poolConfig, shards, alarm));
                        if (oldPool != null) {
                                oldPool.close();
                        }
                }
        }
}
